﻿// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
//
// DirectXPage.xaml.cpp
// Implementation of the DirectXPage class.
//

#include "pch.h"
#include "DirectXPage.xaml.h"
#include "UserSettings.h"

using namespace Weathr;

using namespace concurrency;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;
using namespace Windows::System::Threading;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;

const unsigned int MaxMessages = 5u; // The maximum number of messages to display on the UI

DirectXPage::DirectXPage(Controls::Frame^ rootFrame) : m_windowVisible(true), m_logMessages(ref new Vector<LogMessage^>()), m_actionMessages(ref new Vector<LogMessage^>())
{
    InitializeComponent();

    DataContext = Weathr::UserSettings::Current;

    // Append the frame as a child of the SwapChainPanel.
    swapChainPanel->Children->Append(rootFrame);

    // Register event handlers for page lifecycle.
    CoreWindow^ window = Window::Current->CoreWindow;

    window->VisibilityChanged +=
        ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &DirectXPage::OnVisibilityChanged);

    DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();

    currentDisplayInformation->DpiChanged +=
        ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnDpiChanged);

    currentDisplayInformation->OrientationChanged +=
        ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnOrientationChanged);

    DisplayInformation::DisplayContentsInvalidated +=
        ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnDisplayContentsInvalidated);

    swapChainPanel->CompositionScaleChanged +=
        ref new TypedEventHandler<SwapChainPanel^, Object^>(this, &DirectXPage::OnCompositionScaleChanged);

    swapChainPanel->SizeChanged +=
        ref new SizeChangedEventHandler(this, &DirectXPage::OnSwapChainPanelSizeChanged);

    // Disable all pointer visual feedback for better performance when touching.
    auto pointerVisualizationSettings = PointerVisualizationSettings::GetForCurrentView();
    pointerVisualizationSettings->IsContactFeedbackEnabled = false;
    pointerVisualizationSettings->IsBarrelButtonFeedbackEnabled = false;

    // At this point we have access to the device. 
    // We can create the device-dependent resources.
    m_deviceResources = std::make_shared<DeviceResources>();
    m_deviceResources->SetSwapChainPanel(swapChainPanel);

    // TODO: Investigate background rendering.
    // The comment block below is from the template. The template renders 3D content on a background thread.
    // From my experimentation, background rendering caused the DirectX and XAML content to get out of sync.
    // This app renders 3D content synchronously on the ASTA thread.

    // Register our SwapChainPanel to get independent input pointer events
    //auto workItemHandler = ref new WorkItemHandler([this](IAsyncAction ^)
    //{
    //    // The CoreIndependentInputSource will raise pointer events for the specified device types on whichever thread it's created on.
    //    m_coreInput = swapChainPanel->CreateCoreIndependentInputSource(
    //        Windows::UI::Core::CoreInputDeviceTypes::Mouse |
    //        Windows::UI::Core::CoreInputDeviceTypes::Touch |
    //        Windows::UI::Core::CoreInputDeviceTypes::Pen
    //        );

    //    // Register for pointer events, which will be raised on the background thread.
    //    m_coreInput->PointerPressed += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerPressed);
    //    m_coreInput->PointerMoved += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerMoved);
    //    m_coreInput->PointerReleased += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerReleased);

    //    // Begin processing input messages as they're delivered.
    //    m_coreInput->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    //});

    // Run task on a dedicated high priority background thread.
    // m_inputLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced);

    m_main = std::make_unique<WeathrMain>(m_deviceResources);
    m_main->StartRenderLoop();
    m_renderToken = Windows::UI::Xaml::Media::CompositionTarget::Rendering += ref new EventHandler<Platform::Object^>(this, &DirectXPage::OnRendering);

    // Connect to logger.
    Logger::LogMessageAdded += ref new LogMessageAddedHandler(this, &DirectXPage::OnLogMessageAdded);
    LogMessagesCVS->Source = m_logMessages;
    ActionMessagesCVS->Source = m_actionMessages;

    // Pad out the message list so that the location of other elements are not re-arranged.
    for (unsigned int i = 0; i < MaxMessages; ++i)
    {
        m_logMessages->Append(nullptr);
    }
}

DirectXPage::~DirectXPage()
{
    // Stop rendering and processing events on destruction.
    //m_main->StopRenderLoop();
    Windows::UI::Xaml::Media::CompositionTarget::Rendering -= m_renderToken;
    //m_coreInput->Dispatcher->StopProcessEvents();
}

// Saves the current state of the app for suspend and terminate events.
void DirectXPage::SaveInternalState(IPropertySet^ state)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->Trim();

    // Stop rendering when the app is suspended.
    //m_main->StopRenderLoop();
    Windows::UI::Xaml::Media::CompositionTarget::Rendering -= m_renderToken;

    // Put code to save app state here.
}

// Loads the current state of the app for resume events.
void DirectXPage::LoadInternalState(IPropertySet^ state)
{
    // Put code to load app state here.

    // Start rendering when the app is resumed.
    //m_main->StartRenderLoop();
    m_renderToken = Windows::UI::Xaml::Media::CompositionTarget::Rendering += ref new EventHandler<Platform::Object^>(this, &DirectXPage::OnRendering);
}

// Window event handlers.

void DirectXPage::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
    m_windowVisible = args->Visible;
    if (m_windowVisible)
    {
        //m_main->StartRenderLoop();
        m_renderToken = Windows::UI::Xaml::Media::CompositionTarget::Rendering += ref new EventHandler<Platform::Object^>(this, &DirectXPage::OnRendering);
    }
    else
    {
        //m_main->StopRenderLoop();
        Windows::UI::Xaml::Media::CompositionTarget::Rendering -= m_renderToken;
    }
}

// Display information event handlers.

void DirectXPage::OnDpiChanged(DisplayInformation^ sender, Object^ args)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->SetDpi(sender->LogicalDpi);
    m_main->CreateWindowSizeDependentResources();
}

void DirectXPage::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->SetCurrentOrientation(sender->CurrentOrientation);
    m_main->CreateWindowSizeDependentResources();
}


void DirectXPage::OnDisplayContentsInvalidated(DisplayInformation^ sender, Object^ args)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->ValidateDevice();
}

void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
    m_main->Update();

    if (m_main->Render())
    {
        m_deviceResources->Present();
    }
}

void DirectXPage::OnCompositionScaleChanged(SwapChainPanel^ sender, Object^ args)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->SetCompositionScale(sender->CompositionScaleX, sender->CompositionScaleY);
    m_main->CreateWindowSizeDependentResources();
}

void DirectXPage::OnSwapChainPanelSizeChanged(Object^ sender, SizeChangedEventArgs^ e)
{
    critical_section::scoped_lock lock(m_main->GetCriticalSection());
    m_deviceResources->SetLogicalSize(e->NewSize);
    m_main->CreateWindowSizeDependentResources();
}

// Handles incoming log messages.
void DirectXPage::OnLogMessageAdded(Object^ sender, LogMessage^ message)
{
    using namespace Platform::Collections::Details;

    (void) sender;

    // Append asynchronous activities to the action list.
    if (message->Kind == LogMessageKind::AsyncAction)
    {
        m_actionMessages->Append(message);
        return;
    }

    // Remove the message from the action messages if needed.
    auto pr = [message](VectorProxy<LogMessage^>& iter)
    {
        return ((message->Text == (iter)->Text + "done") || (message->Text == (iter)->Text + "failed"));
    };
    for (auto n = end(m_actionMessages) - remove_if(begin(m_actionMessages), end(m_actionMessages), pr); n > 0; --n)
    {
        m_actionMessages->RemoveAtEnd();
    }

    // Append the message and limit the number of visible messages to MaxMessages.
    m_logMessages->Append(message);
    if (m_logMessages->Size > MaxMessages)
    {
        m_logMessages->RemoveAt(0u);
    }
}
